package jmine.tec.datadigester.listener;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;

import jmine.tec.utils.date.DateUtil;

import org.apache.log4j.Logger;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

import bancosys.tec.datadigester.DataDigesterController;
import bancosys.tec.datadigester.decompressor.impl.ZIPDecompressor;
import bancosys.tec.persist.audit.AuditManager;
import bancosys.tec.persist.audit.AuditStatus;
import bancosys.tec.persist.authorization.AuthorizationManager;
import bancosys.tec.persist.authorization.AuthorizationStatus;
import bancosys.tec.persist.spring.transaction.PartitionTransactionDefinition;
import bancosys.tec.rtm.impl.RtmController;
import bancosys.tec.utils.date.Date;

/**
 * @author takeshi
 */
public class DigesterMessageListener implements MessageListener {

    private static Logger log = Logger.getLogger(DigesterMessageListener.class);

    private static final int BUFFER_SIZE = 1024 * 2;

    /**
     * Controller do datadigester responsável por transformar o arquivo em um DigesterBean.
     */
    private DataDigesterController digesterController;

    private RtmController rtmController;

    private AuthorizationManager authorizationManager;

    private AuditManager auditManager;

    private PlatformTransactionManager txManager;

    /**
     * Devolve o MessageId de uma mensagem.
     * 
     * @param message mensagem
     * @return messageId
     */
    private String getMessageId(Message message) {
        try {
            return message.getJMSMessageID();
        } catch (JMSException e) {
            throw new RuntimeException("Erro ao obter MessageId.", e);
        }
    }

    /**
     * Chamado pelo container para tratar uma mensagem.
     * 
     * @param message mensagem
     */
    public void onMessage(Message message) {
        String messageId = this.getMessageId(message);
        log.info("Mensagem recebida. MessageId:" + messageId);
        if (!(message instanceof BytesMessage)) {
            throw new RuntimeException("Mensagem não é do tipo javax.jms.BytesMessage. MessageId: " + messageId);
        }

        BytesMessage bytesMesage = (BytesMessage) message;
        String fileCode = this.getFileCode(bytesMesage, messageId);
        String fileName = this.getFileName(bytesMesage, messageId);
        Date fileDate = null;

        if (this.getFileDate(bytesMesage, messageId) != null) {
            fileDate = new Date(DateUtil.safeParse(this.getFileDate(bytesMesage, messageId), "yyyy-MM-dd"));
        }

        InputStream physicalFile = this.readBytes(bytesMesage, fileCode, messageId);

        log.info("Recuperado código do arquivo e conteúdo. FileCode: " + fileCode + ". PhysicalFile: " + physicalFile + ". MessageId: "
                + messageId);

        PartitionTransactionDefinition txDefinition = new PartitionTransactionDefinition();
        txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus txStatus = this.txManager.getTransaction(txDefinition);

        try {
            this.setThreadAuthorizationStatus(AuthorizationStatus.DISABLED);
            this.setThreadAuditStatus(AuditStatus.DISABLED);
            this.digesterController.process(fileCode, fileName, fileDate, "DataFeeder", physicalFile, new ZIPDecompressor(), null);
            this.txManager.commit(txStatus);
            log.info("Mensagem tratada com sucesso. MessageId: " + messageId + ". FileCode: " + fileCode);
        } catch (RuntimeException e) {
            log.error("Erro ao tratar mensagem. MessageId: " + messageId + ". FileCode: " + fileCode, e);
            this.rtmController.monitor(e);
            this.txManager.rollback(txStatus);
            throw e;
        } catch (Exception e) {
            log.error("Erro ao tratar mensagem. MessageId: " + messageId + ". FileCode: " + fileCode, e);
            this.rtmController.monitor(e);
            this.txManager.rollback(txStatus);
            throw new RuntimeException(e);
        } finally {
            this.setThreadAuthorizationStatus(AuthorizationStatus.ENABLED);
            this.setThreadAuditStatus(AuditStatus.ENABLED);
        }
    }

    /**
     * Retorna o codigo do arquivo.
     * 
     * @param bytesMesage mensagem
     * @param messageId codigo da mensagem
     * @return data do arquivo
     */
    private String getFileDate(BytesMessage bytesMesage, String messageId) {
        String fileDate;
        try {
            fileDate = bytesMesage.getStringProperty("fileDate");
        } catch (JMSException e) {
            throw new RuntimeException("Erro ao obter FileDate. MessageId: " + messageId, e);
        }

        return fileDate;
    }
    
    /**
     * Retorna o nome do arquivo.
     * 
     * @param bytesMesage mensagem
     * @param messageId codigo da mensagem
     * @return nome do arquivo
     */
    private String getFileName(BytesMessage bytesMesage, String messageId) {
        String fileName;
        try {
            fileName = bytesMesage.getStringProperty("fileName");
        } catch (JMSException e) {
            throw new RuntimeException("Erro ao obter FileName. MessageId: " + messageId, e);
        }

        return fileName;
    }

    /**
     * Retorna o codigo do arquivo.
     * 
     * @param bytesMesage mensagem
     * @param messageId codigo da mensagem
     * @return codigo do arquivo
     */
    private String getFileCode(BytesMessage bytesMesage, String messageId) {
        String fileCode;
        try {
            fileCode = bytesMesage.getStringProperty("fileCode");
        } catch (JMSException e) {
            throw new RuntimeException("Erro ao obter FileCode. MessageId: " + messageId, e);
        }
        if (fileCode == null) {
            throw new RuntimeException("FileCode não foi informado no cabeçalho da mensagem. MessageId: " + messageId);
        }
        return fileCode;
    }

    /**
     * Le os dados da mensagem.
     * 
     * @param message mensagem
     * @param fileCode codigo do arquivo
     * @param messageId id da mensagem
     * @return os dados da mensagem
     */
    public InputStream readBytes(BytesMessage message, String fileCode, String messageId) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead = 0;
        while (bytesRead != -1) {
            try {
                bytesRead = message.readBytes(buffer);
            } catch (JMSException e) {
                throw new RuntimeException("Erro ao ler arquivo da mensagem. MessageId: " + messageId + ". FileCode: " + fileCode, e);
            }
            if (bytesRead != -1) {
                os.write(buffer, 0, bytesRead);
            }
        }
        return new ByteArrayInputStream(os.toByteArray());
    }

    /**
     * Seta o status da authorizacao para a thread corrente.
     * 
     * @param authorizationStatus authorizationStatus
     */
    private void setThreadAuthorizationStatus(AuthorizationStatus authorizationStatus) {
        this.authorizationManager.setThreadAuthorizationStatus(authorizationStatus);
    }

    /**
     * Seta o status da auditoria para a thread corrente.
     * 
     * @param auditStatus auditStatus
     */
    private void setThreadAuditStatus(AuditStatus auditStatus) {
        this.auditManager.setThreadAuditStatus(auditStatus);
    }

    /**
     * @param digesterController the digesterController to set
     */
    public void setDigesterController(DataDigesterController digesterController) {
        this.digesterController = digesterController;
    }

    /**
     * @param rtmController the rtmController to set
     */
    public void setRtmController(RtmController rtmController) {
        this.rtmController = rtmController;
    }

    /**
     * @param authorizationManager the authorizationManager to set
     */
    public void setAuthorizationManager(AuthorizationManager authorizationManager) {
        this.authorizationManager = authorizationManager;
    }

    /**
     * @param auditManager the auditManager to set
     */
    public void setAuditManager(AuditManager auditManager) {
        this.auditManager = auditManager;
    }

    /**
     * @param txManager the txManager to set
     */
    public void setTxManager(PlatformTransactionManager txManager) {
        this.txManager = txManager;
    }

}
